﻿#region MyJScript - LGPL Copyright (c) 2008 Lionel Laské
//
// This file is part of MyJScript.
//
// MyJScript is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// MyJScript is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with MyJScript; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//
//================================================================================
#endregion

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.Scripting;
using Microsoft.Scripting.Hosting;
using Microsoft.Scripting.Runtime;
using MyJScript.Compiler;
using MyJScript.DLR;
using IronPython.Runtime;


namespace MyJScript.Test
{
    /// <summary>
    /// Unit testing for MyJScript compiler
    /// </summary>
    [TestClass]
    public class CompilerTest
    {
        /// <summary>
        /// Constructor
        /// </summary>
        public CompilerTest()
        {
            runtime = null;
            globals = null;
        }

        /// <summary>
        /// DLR runtime
        /// </summary>
        private ScriptRuntime runtime;

        /// <summary>
        /// Global scope shared by scripts
        /// </summary>
        private ScriptScope globals;

        /// <summary>
        /// Set up DLR domain manager
        /// </summary>
        [TestInitialize]
        public void Initialize()
        {
            // Create runtime
            runtime = ScriptRuntime.Create();

            // Preload System assembly
            runtime.LoadAssembly(typeof(string).Assembly);

            // Create a scope at global level
            globals = runtime.CreateScope();
        }

        /// <summary>
        /// Nothing to do now
        /// </summary>
        [TestCleanup]
        public void Cleanup()
        {
        }

        /// <summary>
        /// Compile and run a MyJScript source.
        /// </summary>
        /// <param name="source">source code</param>
        /// <returns>console output</returns>
        private string RunMyJScript(string source)
        {
            // Put output stream to a string
            StringWriter outputString = new StringWriter();
            Console.SetOut(outputString);

            // Load MyJScript Engine
            ScriptEngine myjscript = runtime.GetEngine(typeof(MJSLanguageContext));

            // Execute a MyJScript code
            ScriptSource mjssrc = myjscript.CreateScriptSourceFromString(source);
            mjssrc.Execute(globals);

            // Shutdown MyJScript engine
            myjscript.Shutdown();

            // Reinitialize output stream
            string console = outputString.ToString();
			Stream outputStream = Console.OpenStandardOutput();
            Console.SetOut(new StreamWriter(outputStream));

            return console;
        }

        /// <summary>
        /// Compile and run a IronPython source.
        /// </summary>
        /// <param name="source">source code</param>
        /// <returns>console output</returns>
        private string RunIronPython(string source)
        {
            // Put output stream to a string
            StringWriter outputString = new StringWriter();
            Console.SetOut(outputString);

            // Load IronPython Engine
            ScriptEngine python = runtime.GetEngine(typeof(PythonContext));

            // Execute a IronPython code
            ScriptSource pysrc = python.CreateScriptSourceFromString(source, SourceCodeKind.Statements);
            pysrc.Execute(globals);

            // Shutdown Python engine
            python.Shutdown();

            // Reinitialize output stream
            string console = outputString.ToString();
            Stream outputStream = Console.OpenStandardOutput();
            Console.SetOut(new StreamWriter(outputStream));

            return console;
        }

        /// <summary>
        /// Test Empty program.
        /// </summary>
        [TestMethod]
        public void Empty()
        {
            Assert.AreEqual("", RunMyJScript(""));
        }

        /// <summary>
        /// Test write.
        /// </summary>
        [TestMethod]
        public void Write()
        {
            Assert.AreEqual("Hello world!\r\n", RunMyJScript("write('Hello world!');"));
            Assert.AreEqual("100\r\n", RunMyJScript("write(100);"));
            Assert.AreEqual("Double\"Quote\r\n", RunMyJScript("write('Double\"Quote');"));
            try
            {
                RunMyJScript("write(zzz);");
                Assert.Fail();
            }
            catch (System.MissingMemberException)
            {
            }
        }

        /// <summary>
        /// Test variables.
        /// </summary>
        [TestMethod]
        public void Variables()
        {
            Assert.AreEqual("12\r\nGo!\r\n", RunMyJScript("var a = 12; b = 'Go!'; write(a); write(b);"));
            Assert.AreEqual("100\r\n150\r\n200\r\n100\r\n", RunMyJScript("var _x = 100; write(_x); function hide(_x) { write(_x); _x = 200; write(_x); } hide(150); write(_x);"));
            Assert.AreEqual("30\r\n", RunMyJScript("function param(a) { var a; write(a); } param(30);"));
            Assert.AreEqual("69\r\n", RunMyJScript("function param2(a) { var a = 69; write(a); } param2(12);"));
            Assert.AreEqual("XXX\r\n1000\r\n", RunMyJScript("function doubledecl() { var yan = 'XXX'; write(yan); var yan = 1000; write(yan); } doubledecl();"));
            try
            {
                RunMyJScript("var a; write(a); a = \"Lionel\"; write(a);");
            }
            catch (System.NullReferenceException)
            {
            }
        }

        /// <summary>
        /// Test function call.
        /// </summary>
        [TestMethod]
        public void FunctionCall()
        {
            Assert.AreEqual("1\r\nTwo\r\n", RunMyJScript("function param(a,b) { write(a); write(b); } param(1,'Two');"));
        }

        /// <summary>
        /// Test arithmetic expression.
        /// </summary>
        [TestMethod]
        public void Arithmetic()
        {
            Assert.AreEqual("2100\r\n", RunMyJScript("write(2+'100');"));
            Assert.AreEqual("Hello world!\r\n", RunMyJScript("var a = 'Hello '; var b = \"world!\"; write(a+b);"));
            Assert.AreEqual("154\r\n", RunMyJScript("var quatre=4; var z = 150; z = z + quatre; write(z);"));

            Assert.AreEqual("55\r\n", RunMyJScript("function foo(sub) { write(100-sub); }foo(45);"));
            try
            {
                RunMyJScript("write('Liogo'-'go');");
                Assert.Fail();
            }
            catch (ArgumentTypeException)
            {
            }
            Assert.AreEqual("-1\r\n", RunMyJScript("var one=1; write(one+one-3);"));

            Assert.AreEqual("16\r\n", RunMyJScript("write(2*2*2*2);"));
        }

        /// <summary>
        /// Test while.
        /// </summary>
        [TestMethod]
        public void While()
        {
            Assert.AreEqual("before\r\nafter\r\n", RunMyJScript("write('before'); while (0==1) write('false'); write('after');"));
            Assert.AreEqual("before\r\n0\r\n1\r\n2\r\nafter\r\n", RunMyJScript("var i = 0; write('before'); while (i<3) { write(i); i=i+1; } write('after');"));
            Assert.AreEqual("before\r\n0\r\nA0\r\nA1\r\n1\r\nA0\r\nA1\r\n2\r\nA0\r\nA1\r\nafter\r\n", RunMyJScript("var i = 0; write('before'); while (i<3) { var j = 0; write(i); while(j<2) { write('A'+j); j=j+1; } i=i+1; } write('after');"));

            Assert.AreEqual("before\r\nafter\r\n", RunMyJScript("function foo() { write('before'); while (0==1) write('false'); write('after'); } foo();"));
            Assert.AreEqual("before\r\n0\r\n1\r\n2\r\nafter\r\n", RunMyJScript("function loop(n) { var i = 0; write('before'); while (i<n) { write(i); i=i+1; } write('after'); } loop(3);"));
            Assert.AreEqual("before\r\n0\r\nA0\r\nA1\r\n1\r\nA0\r\nA1\r\n2\r\nA0\r\nA1\r\nafter\r\n", RunMyJScript("function loop() { var i = 0; write('before'); while (i<3) { var j = 0; write(i); while(j<2) { write('A'+j); j=j+1; } i=i+1; } write('after'); } loop();"));
        }

        /// <summary>
        /// Test Return.
        /// </summary>
        [TestMethod]
        public void Return()
        {
            Assert.AreEqual("before\r\n", RunMyJScript("function foo() { write('before'); return; write('after'); } foo();"));
            Assert.AreEqual("before\r\nYes\r\n", RunMyJScript("function foo() { write('before'); return 'Yes'; write('after'); } write(foo());"));
            Assert.AreEqual("before\r\nYes\r\n", RunMyJScript("function foo() { write('before'); while(1==1) { return 'Yes'; } write('after'); } write(foo());"));
            Assert.AreEqual("20\r\n200\r\n", RunMyJScript("function twotime(n) { return 2*n; } write(twotime(10)); write(twotime(100));"));
        }

        /// <summary>
        /// Test IfElse.
        /// </summary>
        [TestMethod]
        public void IfElse()
        {
            Assert.AreEqual("Yes\r\n", RunMyJScript("if(1==1) write('Yes');"));
            Assert.AreEqual("after\r\n", RunMyJScript("if(1==0) write('Yes'); write('after');"));
            Assert.AreEqual("Yes\r\n", RunMyJScript("if(1==1) write('Yes'); else write('No');"));
            Assert.AreEqual("No\r\n", RunMyJScript("if(1==0) write('Yes'); else write('No');"));
            Assert.AreEqual("Yes\r\n2\r\n", RunMyJScript("var i = 1; if(i>0) { write('Yes'); i=i+1; } else { write('No'); i=i-1; } write(i);"));
            Assert.AreEqual("No\r\n-1\r\n", RunMyJScript("var i = 0; if(i>0) { write('Yes'); i=i+1; } else { write('No'); i=i-1; } write(i);"));
            Assert.AreEqual(">10\r\n", RunMyJScript("var i = 34; if(i>10) if (i>100) write('>100'); else write('>10'); else write('<10');"));
            Assert.AreEqual("<10\r\n", RunMyJScript("var i = 8; if(i>10) if (i>100) write('>100'); else write('>10'); else write('<10');"));
        }

        /// <summary>
        /// Test comparison expression.
        /// </summary>
        [TestMethod]
        public void Comparison()
        {
            Assert.AreEqual("No\r\n", RunMyJScript("if(0>1) write('Yes'); else write('No');"));
            Assert.AreEqual("Yes\r\n", RunMyJScript("if(1>0) write('Yes'); else write('No');"));
            Assert.AreEqual("Yes\r\n", RunMyJScript("if(0<1) write('Yes'); else write('No');"));
            Assert.AreEqual("No\r\n", RunMyJScript("if(1<0) write('Yes'); else write('No');"));
            Assert.AreEqual("No\r\n", RunMyJScript("if(0>=1) write('Yes'); else write('No');"));
            Assert.AreEqual("Yes\r\n", RunMyJScript("if(1>=0) write('Yes'); else write('No');"));
            Assert.AreEqual("Yes\r\n", RunMyJScript("if(1>=1) write('Yes'); else write('No');"));
            Assert.AreEqual("Yes\r\n", RunMyJScript("if(0<=1) write('Yes'); else write('No');"));
            Assert.AreEqual("No\r\n", RunMyJScript("if(1<=0) write('Yes'); else write('No');"));
            Assert.AreEqual("Yes\r\n", RunMyJScript("if(1<=1) write('Yes'); else write('No');"));
            Assert.AreEqual("No\r\n", RunMyJScript("if(0==1) write('Yes'); else write('No');"));
            Assert.AreEqual("Yes\r\n", RunMyJScript("if(1==1) write('Yes'); else write('No');"));
            Assert.AreEqual("Yes\r\n", RunMyJScript("if(0!=1) write('Yes'); else write('No');"));
            Assert.AreEqual("No\r\n", RunMyJScript("if(1!=1) write('Yes'); else write('No');"));

            Assert.AreEqual("Yes\r\n", RunMyJScript("if('2'>'10') write('Yes'); else write('No');"));
            Assert.AreEqual("No\r\n", RunMyJScript("if(2>'10') write('Yes'); else write('No');"));
            Assert.AreEqual("No\r\n", RunMyJScript("if('2'>10) write('Yes'); else write('No');"));
            Assert.AreEqual("Yes\r\n", RunMyJScript("if('10'<'2') write('Yes'); else write('No');"));
            Assert.AreEqual("No\r\n", RunMyJScript("if(10<'2') write('Yes'); else write('No');"));
            Assert.AreEqual("No\r\n", RunMyJScript("if('10'<2) write('Yes'); else write('No');"));
            Assert.AreEqual("No\r\n", RunMyJScript("if(2>='10') write('Yes'); else write('No');"));
            Assert.AreEqual("No\r\n", RunMyJScript("if('2'>=10) write('Yes'); else write('No');"));
            Assert.AreEqual("Yes\r\n", RunMyJScript("if('2'>=2) write('Yes'); else write('No');"));
            Assert.AreEqual("No\r\n", RunMyJScript("if(10<='2') write('Yes'); else write('No');"));
            Assert.AreEqual("No\r\n", RunMyJScript("if('10'<=2) write('Yes'); else write('No');"));
            Assert.AreEqual("Yes\r\n", RunMyJScript("if('10'<=10) write('Yes'); else write('No');"));
        }

        /// <summary>
        /// Test a full program.
        /// </summary>
        [TestMethod]
        public void Program()
        {
            Assert.AreEqual("1\r\n24\r\n!5=120\r\n", RunMyJScript(@"
                function fact(n) {
                    if (n==0) return 1;
                    return n*fact(n-1);
                }

                write(fact(1));
                write(fact(4));
                write('!5='+fact(5));
            "));

            Assert.AreEqual("Hello DNG\r\n2\r\n4\r\n6\r\n8\r\n", RunMyJScript(@"
                function twotime(n)
                {
                    return 2*n;
                }

                var i = 1;
                write(""Hello DNG"");
                while(i < 5)
                {
                   write(twotime(i));
                   i = i + 1;
                }
            "));

            Assert.AreEqual("100\r\n200\r\n", RunMyJScript(@"
                a = 200;

                function foo() {
                    var a = 100;
                    write(a);
                }

                function goo() {
                    write(a);
                }

                foo();
                goo();
            "));
        }

        /// <summary>
        /// Test object handling.
        /// </summary>
        [TestMethod]
        public void Object()
        {
            Assert.AreEqual("45\r\n45\r\nMyJScript.Runtime.MyJScriptCallTarget\r\nMyJScript.Runtime.MyJScriptCallTarget\r\n", RunMyJScript(@"
                a = 45;
                write(a);
                write(this.a);
                function foo(n) { write(n); }
                write(foo);
                write(this.foo);
            "));

            Assert.AreEqual("{x:5, y:9, sum:MyJScript.Runtime.MyJScriptCallTarget}\r\n24\r\n", RunMyJScript(@"
                a={};
                a.x=5;
                a.y=a.x+4;
                a.sum=function(z) { return this.x+this.y+z; };
                write(a);
                write(a.sum(10));
            "));

            Assert.AreEqual("Felix said miaow\r\nthis said miaow\r\n", RunMyJScript(@"
                function Cat(name) {
                    this.name = name;
                    this.miaow = function() { write(this.name + ' said miaow'); };
                }

                a = new Cat('Felix');
                a.miaow();
                Cat(""this"");
                this.miaow();
            "));
        }

        /// <summary>
        /// Test build functions
        /// </summary>
        [TestMethod]
        public void BuiltIn()
        {
            Assert.AreEqual("5\r\n<blink>DNG</blink>\r\n<bold>MyJScript</bold>\r\nGuru\r\nNet\r\n", RunMyJScript(@"
                write('hello'.length);
                write('DNG'.blink());
                write('MyJScript'.bold());
                write('DotNetGuru'.substr(6));
                write('DotNetGuru'.substr(3,3));
            "));
        }

        /// <summary>
        /// Test interop with CLR.
        /// </summary>
        [TestMethod]
        public void CLRInterop()
        {
            Assert.AreEqual("6\r\nHELLO!\r\nX45\r\n", RunMyJScript(@"
                a='Hello!';
                write(a.Length);
                write(a.ToUpper());

                b='X';
                write(b+45);
            "));

            Assert.AreEqual(System.DateTime.Now.Year.ToString()+"\r\n", RunMyJScript(@"
                using System;

                var date = System.DateTime;

                var now=date.Now;
                write(now.Year);
            "));
        }

        /// <summary>
        /// Test interop with DLR.
        /// </summary>
        [TestMethod]
        public void DLRInterop()
        {
            // Simple case: use a MyJScript variable in IronPython
            Assert.AreEqual("", RunMyJScript(@"
                name='MyJScript';
            "));
            Assert.AreEqual("Hello MyJScript\r\n", RunIronPython(@"
print 'Hello',name;
            "));

            // Reverse case: use a IronPython variable in MyJScript
            RunIronPython("a=100");

            Assert.AreEqual("100\r\n", RunMyJScript("write(a);"));
        }
    }
}
